#  Copyright 2011 Nokia Siemens Networks Oyj
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

from __future__ import with_statement
import subprocess
import os
import tempfile
import random
import string
from erlastic import decode, encode

import Compare
from asn1_structure import ASN1Factory
from records import Records
from compiler import TemplateCompiler


LOAD = "write"
ENCODE = "encode"
DECODE = "decode"


class ASN1Library(object):

    ROBOT_LIBRARY_SCOPE = 'GLOBAL'

    def __init__(self, compilation_dir=None):
        self._cur_protocol = None
        self._cur_template = None
        self._cur_message = None
        self._asn1_factories = {}
        self._template_compiler = self._get_compiler(compilation_dir)
        self._temp_dir = self._create_temp_dir_for_instance()

    def _get_compiler(self, output_folder):
        if not output_folder:
            output_folder = os.path.join(tempfile.gettempdir(), 'asn1_templates')
        self._set_templates_to_erlang_path(output_folder)
        output_folder = os.path.join(output_folder, 'ebin')
        if not os.path.exists(output_folder):
            os.makedirs(output_folder)
        return TemplateCompiler(output_folder)

    def _set_templates_to_erlang_path(self, output_folder):
        erl_path = os.environ.get('ERL_LIBS','')
        erl_path = output_folder+os.pathsep+erl_path
        os.environ['ERL_LIBS'] = erl_path
        os.putenv('ERL_LIBS', erl_path)

    def _get_asn1_factory(self, protocol):
        if protocol not in self._asn1_factories:
            self._load_factory(protocol)
        return self._asn1_factories[protocol]

    def _load_factory(self, protocol):
        # FIXME: This can not stay like this. We need a plan for the path handling
        root = os.path.dirname(__file__)
        with open(os.path.join(root,'..','..','ebin','%s.hrl' % protocol)) as input:
            self._asn1_factories[protocol] = ASN1Factory(Records(input.read()))

    def _create_temp_dir_for_instance(self):
        random_name = ''.join(random.choice(string.letters + string.digits) for i in xrange(10))
        path = os.path.join(tempfile.gettempdir(), 'asn1_binaries_'+random_name)
        os.mkdir(path)
        return path

    def add_templates(self, template_folder):
        self._template_compiler.add_templates(template_folder)
        self._template_compiler.compile()

    def load_message_template(self, protocol, template, message=None):
        if message is None:
            message=template
        temp_file = self._get_erlang_bin_path(template)
        self._remove(temp_file)
        self._execute_erl_cmd(LOAD, template, message, temp_file)
        if not os.path.exists(temp_file):
            raise Exception("Template '%s' not found" % template)
        self._cur_message = self._get_template(protocol, template)
        self._cur_protocol = protocol
        self._cur_template = template

    def log_message(self, message=None, level='INFO'):
        if not message:
            message = self._cur_message
        if not message:
            raise Exception('No message to log.')
        print '*%s* %s' % (level, str(message))

    def encode_message(self):
        temp_file = self._get_erlang_bin_path(self._cur_template)
        self._execute_erl_cmd(ENCODE, self._cur_protocol, temp_file)
        msg = self._get_encoded_message(self._cur_protocol, self._cur_template)
        print '*INFO* Message %r' % msg
        return msg

    def decode_message(self, protocol, data):
        asn1_bin_file = self._get_asn1_enc_path(protocol)
        with open(asn1_bin_file, 'wb') as outfile:
            outfile.write(data)
        asn1_dec_bin_file = self._get_asn1_decoded_bin_path(protocol)
        self._execute_erl_cmd(DECODE, protocol, asn1_bin_file, asn1_dec_bin_file)
        with open(asn1_dec_bin_file, 'r') as in_file:
            self._cur_message = self._get_asn1_factory(protocol).parse(decode(in_file.read()))
        self._cur_protocol = protocol
        print "*DEBUG* Decoded message: '%s'" % str(self._cur_message)
        return self._cur_message

    def _get_template(self, protocol, template):
        temp_file = self._get_erlang_bin_path(template)
        with open(temp_file, 'r') as in_file:
            return self._get_asn1_factory(protocol).parse(decode(in_file.read()))

    def get_field_as_string(self, path):
        return self._get_raw(path, self._cur_message).string

    def _get_raw(self, path, message):
        splitted_path = path.split(".")
        return message.path(splitted_path)

    def get_field_as_list(self, path):
        return self._get_raw(path, self._cur_message).list

    def get_field_as_int(self, path):
        return self._get_raw(path, self._cur_message).int

    def get_field_name(self, path):
        return self._get_raw(path, self._cur_message).name

    def set_field(self, path, value):
        node = self._get_raw(path, self._cur_message)
        node.set(value)
        temp_file = self._get_erlang_bin_path(self._cur_template)
        with open(temp_file, 'wb') as out_file:
            out_file.write(encode(self._cur_message.erlastic))

    def teardown_asn1lib(self):
        p = subprocess.Popen(['erl_call', '-sname', 'rammbock', '-q'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
        rc_erl = p.wait()
        p = subprocess.Popen(['epmd', '-kill'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
        rc_epmd = p.wait()
        if rc_erl is 0 and rc_epmd is 0:
            print 'Stopped Erlang node'
        else:
            print 'Shutting down Erlang node failed. Return codes erl:%d, epmd:%d' % (rc_erl, rc_epmd)
        self._del_temp_folder()

    def _del_temp_folder(self):
        for name in os.listdir(self._temp_dir):
            os.remove(os.path.join(self._temp_dir,name))
        os.rmdir(self._temp_dir)

    def _remove(self, path):
        try:
            os.remove(path)
        except Exception, e:
            pass

    def _get_encoded_message(self, protocol, template):
        with open(self._get_erlang_enc_path(template)) as f:
            return f.read()

    def _execute_erl_cmd(self, function, *args):
        cmd = ('erl_call', '-sname', 'rammbock', '-s', '-a', 'binary_writer ' + function + ' [[' + ",".join('"' + a + '"' for a in args) + ']]')
        print "*DEBUG* executing erlang: %s" % " ".join(cmd)
        # TODO: how to get the output to log, but not on prompt?
        p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
        output = p.communicate()[0]
        rc = p.poll()
        if rc is not 0:
            raise Exception('Executing erl_call failed with rc %d. Output from call %s' % (rc, output))
        print '*DEBUG* executed erl_call with output "%s"' % output

    def _get_erlang_enc_path(self, name):
        return  self._get_erlang_bin_path(name)+'.enc'

    def _get_erlang_bin_path(self, name):
        return  os.path.join(self._temp_dir,name+'.bin')

    def _get_asn1_enc_path(self, module):
        return  os.path.join(self._temp_dir,module+'.asn1.bin')

    def _get_asn1_decoded_bin_path(self, module):
        return  os.path.join(self._temp_dir,module+'.dec.bin')

    def validate_message(self, message, protocol, template, validation_template):
        self.decode_message(protocol, message)
        actual = self._cur_message
        self.load_message_template(protocol, template, validation_template)
        expected = self._get_template(protocol, validation_template)
        Compare.compare(expected.erlastic, actual.erlastic)
